﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Linq.Expressions;
using System.Diagnostics.Contracts;
using System.Threading;
using System.Reflection;
using System.ComponentModel;

namespace ReactiveCode.Observables
{
    using Util;
    using Observables;

    using MemberListenerFactory = Func<MemberInfo, MemberListener>;

    public delegate IObservable<object> MemberListener(object instance, object[] memberArguments);
    public static partial class ChangeListening
    {
        static List<MemberListenerFactory> _stdFactories = new List<MemberListenerFactory>();
        static List<MemberListenerFactory> _userFactories = new List<MemberListenerFactory>();
        static bool _loadingStandard;

        static ReaderWriterLockSlim _lock = new ReaderWriterLockSlim();

        public static MemberListener CreateListener(MemberInfo member)
        {
            return CreateListener(member, true);
        }
        internal static MemberListener CreateListener(MemberInfo member, bool allowDefault)
        {
            _lock.EnterReadLock();
            try
            {
                foreach (var factoryFunc in _userFactories.Concat(_stdFactories))
                {
                    var factory = factoryFunc(member);
                    if (factory != null)
                        return factory;
                }
            }
            finally
            {
                _lock.ExitReadLock();
            }

            if (!allowDefault)
                return null;

            var getter = ReflectionUtil.ToGetter(member);
            return (obj, args) => Observable.Return(getter(obj, args));
        }

        public static IDisposable RegisterListener(MemberListenerFactory factory)
        {
            Contract.Requires(factory != null);

            var list = _loadingStandard ? _stdFactories : _userFactories;

            _lock.EnterWriteLock();
            try
            {
                list.Add(factory);
                return Disposable.Create(() => list.Remove(factory));
            }
            finally
            {
                _lock.ExitWriteLock();
            }
        }
        public static IDisposable RegisterListener(Func<PropertyInfo, MemberListener> factory)
        {
            Contract.Requires(factory != null);
            return RegisterListener((MemberInfo m) => (m is PropertyInfo) ? factory(m as PropertyInfo) : null);
        }

        public static IObservable<object> Observe(object instance, MemberInfo member)
        {
            return CreateListener(member)(instance, null);
        }
    }
}